
import { Vector3 } from "three";
import { App } from "../Engine";
import Config from "../application/constants/Config";
import Const from "../application/constants/Const";
import Pixel from "../renderExtension/types/Pixel";
import { Point } from "../renderExtension/types/Point";

/** 百度坐标、高德坐标、经纬度坐标转换工具 */
export default class GPSUtils {
    private static x_pi = Math.PI * 3000.0 / 180.0;
    /**  WGS-84 to GCJ-02 */
    public static gcj_encrypt(wgsLng: number, wgsLat: number) {
        if (GPSUtils.outOfChina(wgsLng, wgsLat))
            return [wgsLng, wgsLat];
        let d = GPSUtils.delta(wgsLng, wgsLat);
        return [wgsLng + d[0], wgsLat + d[1]];
    }

    /** GCJ-02 to WGS-84 exactly */
    public static gcj_decrypt_exact(gcjLng: number, gcjLat: number) {
        let initDelta = 0.01;
        let threshold = 0.000000001;
        let dLat = initDelta, dLng = initDelta;
        let mLat = gcjLat - dLat, mLng = gcjLng - dLng;
        let pLat = gcjLat + dLat, pLng = gcjLng + dLng;
        let wgsLat, wgsLng, i = 0;
        while (1) {
            wgsLat = (mLat + pLat) / 2;
            wgsLng = (mLng + pLng) / 2;
            let tmp = GPSUtils.gcj_encrypt(wgsLng, wgsLat);
            dLat = tmp[1] - gcjLat;
            dLng = tmp[0] - gcjLng;
            if ((Math.abs(dLat) < threshold) && (Math.abs(dLng) < threshold))
                break;
            if (dLat > 0) pLat = wgsLat; else mLat = wgsLat;
            if (dLng > 0) pLng = wgsLng; else mLng = wgsLng;
            if (++i > 10000) break;
        }
        return [wgsLng, wgsLat];
    }

    /** GCJ-02 to BD-09 */
    public static bd_encrypt(gcjLng: number, gcjLat: number) {
        let x = gcjLng, y = gcjLat;
        let z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * GPSUtils.x_pi);
        let theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * GPSUtils.x_pi);
        let bdLng = z * Math.cos(theta) + 0.0065;
        let bdLat = z * Math.sin(theta) + 0.006;
        return [bdLng, bdLat];
    }

    /**BD-09 to WGS-84 */
    public static bd2wgs84(bdLng: number, bdLat: number) {
        let position = this.bd_decrypt(bdLng, bdLat);
        return this.gcj_decrypt_exact(position[0], position[1]);
    }

    /** BD-09 to GCJ-02 */
    public static bd_decrypt(bdLng: number, bdLat: number) {
        let x = bdLng - 0.0065, y = bdLat - 0.006;
        let z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * GPSUtils.x_pi);
        let theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * GPSUtils.x_pi);
        let gcjLng = z * Math.cos(theta);
        let gcjLat = z * Math.sin(theta);
        return [gcjLng, gcjLat];
    }
    public static delta(lng: number, lat: number) {
        let a = 6378245.0;
        let ee = 0.00669342162296594323; //  ee: 椭球的偏心率。
        let dLat = this.transformLat(lng - 105.0, lat - 35.0);
        let dLng = this.transformLng(lng - 105.0, lat - 35.0);
        let radLat = lat / 180.0 * Math.PI;
        let magic = Math.sin(radLat);
        magic = 1 - ee * magic * magic;
        let sqrtMagic = Math.sqrt(magic);
        dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * Math.PI);
        dLng = (dLng * 180.0) / (a / sqrtMagic * Math.cos(radLat) * Math.PI);
        return [dLng, dLat];
    }
    public static transformLat(x: number, y: number): number {
        let ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(y * Math.PI) + 40.0 * Math.sin(y / 3.0 * Math.PI)) * 2.0 / 3.0;
        ret += (160.0 * Math.sin(y / 12.0 * Math.PI) + 320 * Math.sin(y * Math.PI / 30.0)) * 2.0 / 3.0;
        return ret;
    }

    public static transformLng(x: number, y: number): number {
        let ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(x * Math.PI) + 40.0 * Math.sin(x / 3.0 * Math.PI)) * 2.0 / 3.0;
        ret += (150.0 * Math.sin(x / 12.0 * Math.PI) + 300.0 * Math.sin(x / 30.0 * Math.PI)) * 2.0 / 3.0;
        return ret;
    }
    /**
     * 判断是否超出中国范围
     * @param lng 
     * @param lat 
     */
    public static outOfChina(lng: number, lat: number) {
        if (lng < 72.004 || lng > 137.8347)
            return true;
        if (lat < 0.8293 || lat > 55.8271)
            return true;
        return false;
    }
    /** 批量其他平台坐标转换本系统坐标 */
    public static convertFrom(data, type: string, callback: Function) {
        if (type === Const.EPSGType.GCJ02) {
            callback({ info: 'ok', locations: data });
            return;
        }

        if (data instanceof Point) {
            data = [data];
        }

        let l = data.map(v => {
            if (type === Const.EPSGType.BD09) {
                return GPSUtils.bd_decrypt(v.getLng(), v.getLat());
            } else {//WGS84
                return GPSUtils.gcj_encrypt(v.getLng(), v.getLat());
            }
        });
        callback({ info: 'ok', locations: l });
    }
    /** GCJ02转墨卡托,系统内默认GCJ02为经纬度 */
    // public static lngLat2Mercator(lng: number, lat: number) {
    //     lng += 180.0;
    //     while (lng >= 360.0)
    //         lng -= 360.0;
    //     while (lng < 0.0)
    //         lng += 360.0;
    //     let ox = lng * Const.Number.DIAMETER / 360.0;
    //     if (lat > 85.0)
    //         lat = 85.0;
    //     if (lat < -85.0)
    //         lat = -85.0;
    //     let phi = Math.sin(Math.PI * lat / 180.0);
    //     phi = 0.5 * Math.log((1 + phi) / (1 - phi));
    //     let oy = ((1 - phi / Math.PI) / 2) * Const.Number.DIAMETER;
    //     return { x: ox, y: oy };
    // }
    // /** 墨卡托转GCJ02 */
    // public static mercator2LngLat(x: number, y: number) {
    //     let lng = x * 360.0 / Const.Number.DIAMETER - 180.0;
    //     let lat = (2.0 - y / 268435456.0) * Math.PI;
    //     lat = Math.pow(Math.E, lat);
    //     lat = Math.asin((lat - 1.0) / (lat + 1.0));
    //     lat = 180.0 * lat / Math.PI;
    //     return { lng: lng, lat: lat };
    // }

    /**WebMercator（M)->WGS84 */
    public static WebtoP84(x: number, y: number) {
        return { x: this.WebtoP84_X(x), y: this.WebtoP84_Y(y) };
    }
    /**WGS84->WebMercator（M) */
    public static P84toWeb(x: number, y: number) {
        return { x: this.P84ToWeb_X(x), y: this.P84ToWeb_Y(y) };
    }
    public static WebtoP84_Y(y: number) {
        y = y / 111319.4907777778;
        return 180 / Math.PI * (2 * Math.atan(Math.exp(y * Math.PI / 180)) - Math.PI / 2);
    }
    public static WebtoP84_X(x: number) {
        return x / 111319.4907777778;
    }
    public static P84ToWeb_Y(y: number) {
        y = Math.log(Math.tan((90 + y) * Math.PI / 360)) / (Math.PI / 180);
        y = y * 111319.4907777778;
        return y;
    }
    public static P84ToWeb_X(x: number) {
        return x * 111319.4907777778;
    }

    /** 平面地图坐标转换为地图经纬度坐标 */
    public static pixelToPoint(pixel: Pixel, level?: number): Point {
        if (undefined === level) {
            level = App.getInstance().map.zoom
        }
        let zoomval = Config.Map.zoomVals[level - 1];
        let v3 = new Vector3(pixel.getX() * zoomval, pixel.getY() * zoomval, pixel.getZ());
        let point = new Point().fromVector3(v3);
        return point;
    }

    /** 地图经纬度坐标转换为平面地图坐标 */
    public static PointToPixel(point: Point, level?: number): Pixel {
        if (undefined === level) {
            level = App.getInstance().map.zoom;
        }
        let zoomval = Config.Map.zoomVals[level - 1];
        let mxy = point.toPixel();
        return new Pixel(mxy.getX() / zoomval, mxy.getY() / zoomval, mxy.getZ());
    }
    /** 地图经纬度坐标转为地图容器像素坐标 */
    public static PointToContainer(point: any): Pixel {
        if (point.toVector3)
            point = point.toVector3();
        let size = App.getInstance().getSize();
        point.project(App.getInstance().three.camera);
        return new Pixel(0.5 * (size.getWidth() * (point.x + 1)), 0.5 * (size.getHeight() * (1 - point.y)), point.z);
    }
    /** 地图容器像素坐标转为地图坐标 */
    public static containerToVector(pixel: Pixel): Vector3 {
        let size = App.getInstance().getSize();
        let point = new Vector3().set((pixel.getX() / size.getWidth()) * 2 - 1, -(pixel.getY() / size.getHeight()) * 2 + 1, 0.5);
        point.unproject(App.getInstance().three.camera);
        return point;
    }
    /** 地图坐标转为地图容器像素坐标 */
    public static vectorToContainer(point: Vector3): Pixel {
        let size = App.getInstance().getSize();
        point.project(App.getInstance().three.camera);
        return new Pixel(0.5 * (size.getWidth() * (point.x + 1)), 0.5 * (size.getHeight() * (1 - point.y)));
    }

}